// SPDX-License-Identifier: Apache-2.0

package chisel3

import chisel3.experimental.dataview.reify

import scala.language.experimental.macros
import chisel3.experimental.{Analog, BaseModule}
import chisel3.experimental.{prefix, SourceInfo, UnlocatableSourceInfo}
import chisel3.experimental.dataview.reifySingleData
import chisel3.internal.Builder.pushCommand
import chisel3.internal._
import chisel3.internal.sourceinfo._
import chisel3.internal.firrtl._
import chisel3.reflect.DataMirror
import chisel3.util.simpleClassName

import scala.reflect.ClassTag
import scala.util.Try

sealed abstract class SpecifiedDirection
object SpecifiedDirection {

  case object Unspecified extends SpecifiedDirection

  case object Output extends SpecifiedDirection

  case object Input extends SpecifiedDirection

  case object Flip extends SpecifiedDirection

  def flip(dir: SpecifiedDirection): SpecifiedDirection = dir match {
    case Unspecified => Flip
    case Flip        => Unspecified
    case Output      => Input
    case Input       => Output
  }

  def fromParent(parentDirection: SpecifiedDirection, thisDirection: SpecifiedDirection): SpecifiedDirection =
    (parentDirection, thisDirection) match {
      case (SpecifiedDirection.Output, _)                  => SpecifiedDirection.Output
      case (SpecifiedDirection.Input, _)                   => SpecifiedDirection.Input
      case (SpecifiedDirection.Unspecified, thisDirection) => thisDirection
      case (SpecifiedDirection.Flip, thisDirection)        => SpecifiedDirection.flip(thisDirection)
    }

  private[chisel3] def specifiedDirection[T <: Data](
    source: => T
  )(dir:    T => SpecifiedDirection
  ): T = {
    val prevId = Builder.idGen.value
    val data = source // evaluate source once (passed by name)
    requireIsChiselType(data)
    val out = if (!data.mustClone(prevId)) data else data.cloneType.asInstanceOf[T]
    out.specifiedDirection = dir(data) // Must use original data, specified direction of clone is cleared
    out
  }

}

sealed abstract class ActualDirection

object ActualDirection {

  case object Empty extends ActualDirection

  case object Unspecified extends ActualDirection

  case object Output extends ActualDirection

  case object Input extends ActualDirection

  sealed abstract class BidirectionalDirection
  case object Default extends BidirectionalDirection
  case object Flipped extends BidirectionalDirection

  case class Bidirectional(dir: BidirectionalDirection) extends ActualDirection

  def fromSpecified(direction: SpecifiedDirection): ActualDirection = direction match {
    case SpecifiedDirection.Unspecified | SpecifiedDirection.Flip => ActualDirection.Unspecified
    case SpecifiedDirection.Output                                => ActualDirection.Output
    case SpecifiedDirection.Input                                 => ActualDirection.Input
  }

  def fromChildren(
    childDirections:    Set[ActualDirection],
    containerDirection: SpecifiedDirection
  ): Option[ActualDirection] = {
    if (childDirections == Set()) { // Sadly, Scala can't do set matching
      ActualDirection.fromSpecified(containerDirection) match {
        case ActualDirection.Unspecified => Some(ActualDirection.Empty) // empty direction if relative / no direction
        case dir                         => Some(dir) // use assigned direction if specified
      }
    } else if (childDirections == Set(ActualDirection.Unspecified)) {
      Some(ActualDirection.Unspecified)
    } else if (childDirections == Set(ActualDirection.Input)) {
      Some(ActualDirection.Input)
    } else if (childDirections == Set(ActualDirection.Output)) {
      Some(ActualDirection.Output)
    } else if (
      childDirections.subsetOf(
        Set(
          ActualDirection.Output,
          ActualDirection.Input,
          ActualDirection.Bidirectional(ActualDirection.Default),
          ActualDirection.Bidirectional(ActualDirection.Flipped)
        )
      )
    ) {
      containerDirection match {
        case SpecifiedDirection.Unspecified => Some(ActualDirection.Bidirectional(ActualDirection.Default))
        case SpecifiedDirection.Flip        => Some(ActualDirection.Bidirectional(ActualDirection.Flipped))
        case _                              => throw new RuntimeException("Unexpected forced Input / Output")
      }
    } else {
      None
    }
  }
}

private[chisel3] object cloneSupertype {
  def apply[T <: Data](
    elts:        Seq[T],
    createdType: String
  )(
    implicit sourceInfo: SourceInfo
  ): T = {
    require(!elts.isEmpty, s"can't create $createdType with no inputs")

    val filteredElts = elts.filter(_ != DontCare)
    require(!filteredElts.isEmpty, s"can't create $createdType with only DontCare inputs")

    if (filteredElts.head.isInstanceOf[Bits]) {
      val model: T = filteredElts.reduce { (elt1: T, elt2: T) =>
        ((elt1, elt2) match {
          case (elt1: Bool, elt2: Bool) => elt1
          case (elt1: Bool, elt2: UInt) => elt2 // TODO: what happens with zero width UInts?
          case (elt1: UInt, elt2: Bool) => elt1 // TODO: what happens with zero width UInts?
          case (elt1: UInt, elt2: UInt) =>
            // TODO: perhaps redefine Widths to allow >= op?
            if (elt1.width == (elt1.width.max(elt2.width))) elt1 else elt2
          case (elt1: SInt, elt2: SInt) => if (elt1.width == (elt1.width.max(elt2.width))) elt1 else elt2
          case (elt1, elt2) =>
            throw new AssertionError(
              s"can't create $createdType with heterogeneous types ${elt1.getClass} and ${elt2.getClass}"
            )
        }).asInstanceOf[T]
      }
      model.cloneTypeFull
    } else {
      for (elt <- filteredElts.tail) {
        require(
          elt.getClass == filteredElts.head.getClass,
          s"can't create $createdType with heterogeneous types ${filteredElts.head.getClass} and ${elt.getClass}"
        )
        require(
          elt.typeEquivalent(filteredElts.head),
          s"can't create $createdType with non-equivalent types ${filteredElts.head} and ${elt}"
        )
      }
      filteredElts.head.cloneTypeFull
    }
  }
}

private[chisel3] object getRecursiveFields {
  def noPath(data:       Data): Seq[Data] = lazilyNoPath(data).toVector
  def lazilyNoPath(data: Data): Iterable[Data] = DataMirror.collectMembers(data) { case x => x }

  def apply(data:  Data, path: String): Seq[(Data, String)] = lazily(data, path).toVector
  def lazily(data: Data, path: String): Iterable[(Data, String)] = DataMirror.collectMembersAndPaths(data, path) {
    case x => x
  }
}

private[chisel3] object getMatchedFields {
  def apply(x: Data, y: Data): Seq[(Data, Data)] = (x, y) match {
    case (x: Element, y: Element) =>
      require(x.typeEquivalent(y))
      Seq(x -> y)
    case (x: Record, y: Record) =>
      (x._elements
        .zip(y._elements))
        .map {
          case ((xName, xElt), (yName, yElt)) =>
            require(
              xName == yName,
              s"$xName != $yName, ${x._elements}, ${y._elements}, $x, $y"
            ) // assume fields returned in same, deterministic order
            getMatchedFields(xElt, yElt)
        }
        .fold(Seq(x -> y)) {
          _ ++ _
        }
    case (x: Vec[_], y: Vec[_]) =>
      (x.elementsIterator
        .zip(y.elementsIterator))
        .map {
          case (xElt, yElt) =>
            getMatchedFields(xElt, yElt)
        }
        .fold(Seq(x -> y)) {
          _ ++ _
        }
  }
}

object chiselTypeOf {
  def apply[T <: Data](target: T): T = {
    requireIsHardware(target)
    target.cloneTypeFull.asInstanceOf[T]
  }
}

object Input {
  def apply[T <: Data](source: => T): T = {
    SpecifiedDirection.specifiedDirection(source)(_ => SpecifiedDirection.Input)
  }
}
object Output {
  def apply[T <: Data](source: => T): T = {
    SpecifiedDirection.specifiedDirection(source)(_ => SpecifiedDirection.Output)
  }
}

object Flipped {
  def apply[T <: Data](source: => T): T = {
    SpecifiedDirection.specifiedDirection(source)(x => SpecifiedDirection.flip(x.specifiedDirection))
  }
}


abstract class Data extends HasId with NamedComponent with SourceInfoDoc {
  import Data.ProbeInfo

  private[chisel3] def flatten: IndexedSeq[Element] = {
    this match {
      case elt: Aggregate => elt.elementsIterator.toIndexedSeq.flatMap { _.flatten }
      case elt: Element   => IndexedSeq(elt)
      case elt => throwException(s"Cannot flatten type ${elt.getClass}")
    }
  }

  private[chisel3] def containsAFlipped: Boolean = false

  private[chisel3] def mustClone(prevId: Long): Boolean = {
    this.hasBinding || this._minId <= prevId
  }

  private[chisel3] def _minId: Long = this._id

  override def autoSeed(name: String): this.type = {
    topBindingOpt match {
      case Some(PortBinding(m)) if hasAutoSeed && Builder.currentModule.contains(m) => this
      case _                                                                        => super.autoSeed(name)
    }
  }

  private var _probeInfoVar:      ProbeInfo = null
  private[chisel3] def probeInfo: Option[ProbeInfo] = Option(_probeInfoVar)
  private[chisel3] def probeInfo_=(probeInfo: Option[ProbeInfo]) = _probeInfoVar = probeInfo.getOrElse(null)

  private var _isConst:         Boolean = false
  private[chisel3] def isConst: Boolean = _isConst
  private[chisel3] def isConst_=(isConst: Boolean) = _isConst = isConst

  private var _specifiedDirection:         SpecifiedDirection = SpecifiedDirection.Unspecified
  private[chisel3] def specifiedDirection: SpecifiedDirection = _specifiedDirection
  private[chisel3] def specifiedDirection_=(direction: SpecifiedDirection) = {
    _specifiedDirection = direction
  }

  private[chisel3] def _assignCompatibilityExplicitDirection: Unit = {
    (this, _specifiedDirection) match {
      case (_: Analog, _) => // nothing to do
      case (_, SpecifiedDirection.Unspecified)                       => _specifiedDirection = SpecifiedDirection.Output
      case (_, SpecifiedDirection.Flip)                              => _specifiedDirection = SpecifiedDirection.Input
      case (_, SpecifiedDirection.Input | SpecifiedDirection.Output) => // nothing to do
    }
  }

  private var _bindingVar: Binding = null // using nullable var for better memory usage
  private def _binding:    Option[Binding] = Option(_bindingVar)
  protected[chisel3] def binding: Option[Binding] = _binding
  protected def binding_=(target: Binding): Unit = {
    if (_binding.isDefined) {
      throw RebindingException(s"Attempted reassignment of binding to $this, from: ${target}")
    }
    _bindingVar = target
  }

  private[chisel3] def hasBinding: Boolean = _binding.isDefined

  private[chisel3] final def isSynthesizable: Boolean = _binding.map {
    case ChildBinding(parent) => parent.isSynthesizable
    case _: TopBinding => true
    case (_: SampleElementBinding[_] | _: MemTypeBinding[_]) => false
  }.getOrElse(false)

  private[chisel3] def topBindingOpt: Option[TopBinding] = _binding.flatMap {
    case ChildBinding(parent) => parent.topBindingOpt
    case bindingVal: TopBinding => Some(bindingVal)
    case SampleElementBinding(parent) => parent.topBindingOpt
    case _: MemTypeBinding[_] => None
  }

  private[chisel3] def topBinding: TopBinding = topBindingOpt.get

  private[chisel3] def bind(target: Binding, parentDirection: SpecifiedDirection = SpecifiedDirection.Unspecified): Unit

  private[chisel3] def maybeAddToParentIds(target: Binding): Unit = {
    target match {
      case c: SecretPortBinding  => // secret ports are handled differently, parent's don't need to know about that
      case c: ConstrainedBinding => _parent.foreach(_.addId(this))
      case _ =>
    }
  }

  private var _directionVar: ActualDirection = null // using nullable var for better memory usage
  private def _direction:    Option[ActualDirection] = Option(_directionVar)

  private[chisel3] def direction: ActualDirection = _direction.get
  private[chisel3] def direction_=(actualDirection: ActualDirection): Unit = {
    if (_direction.isDefined) {
      throw RebindingException(s"Attempted reassignment of resolved direction to $this")
    }
    _directionVar = actualDirection
  }

  private[chisel3] def stringAccessor(chiselType: String): String = {
    val thiz = reifySingleData(this).getOrElse(this)
    thiz.topBindingOpt match {
      case None => chiselType
      case Some(DontCareBinding()) => s"$chiselType(DontCare)"
      case Some(topBinding) =>
        val binding: String = thiz._bindingToString(topBinding)
        val name = thiz.earlyName
        val mod = thiz.parentNameOpt.map(_ + ".").getOrElse("")

        s"$mod$name: $binding[$chiselType]"
    }
  }

  private[chisel3] def _bindingToString(topBindingOpt: TopBinding): String =
    topBindingOpt match {
      case OpBinding(_, _)           => "OpResult"
      case MemoryPortBinding(_, _)   => "MemPort"
      case PortBinding(_)            => "IO"
      case SecretPortBinding(_)      => "IO"
      case RegBinding(_, _)          => "Reg"
      case WireBinding(_, _)         => "Wire"
      case DontCareBinding()         => "(DontCare)"
      case ElementLitBinding(litArg) => "(unhandled literal)"
      case BundleLitBinding(litMap)  => "(unhandled bundle literal)"
      case VecLitBinding(litMap)     => "(unhandled vec literal)"
      case _                         => ""
    }

  private[chisel3] def earlyName: String = Arg.earlyLocalName(this)

  private[chisel3] def parentNameOpt: Option[String] = this._parent.map(_.name)

  private[chisel3] def _localErrorContext: String = {
    if (this.binding.exists(_.isInstanceOf[ChildBinding])) {
      val n = Arg.earlyLocalName(this, includeRoot = false)
      s"Field '$n' of type ${this.typeName}"
    } else {
      this.typeName
    }
  }

  private[chisel3] def allElements: Seq[Element]

  private[chisel3] def badConnect(that: Data)(implicit sourceInfo: SourceInfo): Unit =
    throwException(s"cannot connect ${this} and ${that}")

  private[chisel3] def connect(
    that: Data
  )(
    implicit sourceInfo: SourceInfo
  ): Unit = {
    requireIsHardware(this, "data to be connected")
    requireIsHardware(that, "data to be connected")
    this.topBinding match {
      case _: ReadOnlyBinding => throwException(s"Cannot reassign to read-only $this")
      case _ => // fine
    }

    try {
      MonoConnect.connect(sourceInfo, this, that, Builder.referenceUserContainer)
    } catch {
      case MonoConnectException(message) =>
        throwException(
          s"Connection between sink ($this) and source ($that) failed @: $message"
        )
    }
  }
  private[chisel3] def bulkConnect(
    that: Data
  )(
    implicit sourceInfo: SourceInfo
  ): Unit = {
    requireIsHardware(this, s"data to be bulk-connected")
    requireIsHardware(that, s"data to be bulk-connected")
    (this.topBinding, that.topBinding) match {
      case (_: ReadOnlyBinding, _: ReadOnlyBinding) => throwException(s"Both $this and $that are read-only")
      // DontCare cannot be a sink (LHS)
      case (_: DontCareBinding, _) => throw BiConnect.DontCareCantBeSink
      case _ => // fine
    }
    try {
      BiConnect.connect(sourceInfo, this, that, Builder.referenceUserModule)
    } catch {
      case BiConnectException(message) =>
        throwException(
          s"Connection between left ($this) and source ($that) failed @$message"
        )
    }
  }

  private[chisel3] final def typeEquivalent(
    that: Data
  ): Boolean = findFirstTypeMismatch(that, strictTypes = true, strictWidths = true).isEmpty

  private[chisel3] final def findFirstTypeMismatch(
    that:         Data,
    strictTypes:  Boolean,
    strictWidths: Boolean
  ): Option[String] = {
    def rec(left: Data, right: Data): Option[String] =
      (left, right) match {
        case (e1: EnumType, e2: EnumType) =>
          // TODO, should we implement a form of structural equality for enums?
          if (e1.factory == e2.factory) None
          else Some(s": Left ($e1) and Right ($e2) have different types.")
        case (e1: Element, e2: Element) if e1.getClass == e2.getClass =>
          if (strictWidths && e1.width != e2.width) {
            Some(s": Left ($e1) and Right ($e2) have different widths.")
          } else {
            None
          }
        case (r1: Record, r2: Record) if !strictTypes || r1.getClass == r2.getClass =>
          val (larger, smaller, msg) =
            if (r1._elements.size >= r2._elements.size) (r1, r2, "Left") else (r2, r1, "Right")
          larger._elements.collectFirst {
            case (name, data) =>
              val recurse = smaller._elements.get(name) match {
                case None        => Some(s": Dangling field on $msg")
                case Some(data2) => rec(data, data2)
              }
              recurse.map("." + name + _)
          }.flatten
        case (v1: Vec[_], v2: Vec[_]) =>
          if (v1.size != v2.size) {
            Some(s": Left (size ${v1.size}) and Right (size ${v2.size}) have different lengths.")
          } else {
            val recurse = rec(v1.sample_element, v2.sample_element)
            recurse.map("[_]" + _)
          }
        case _ => Some(s": Left ($left) and Right ($right) have different types.")
      }
    val leftType = if (this.hasBinding) this.cloneType else this
    val rightType = if (that.hasBinding) that.cloneType else that
    rec(leftType, rightType)
  }

  private[chisel3] def requireVisible(): Unit = {
    val mod = topBindingOpt.flatMap(_.location)
    topBindingOpt match {
      case Some(tb: TopBinding) if (mod == Builder.currentModule) =>
      case Some(pb: PortBinding)
          if (mod.flatMap(Builder.retrieveParent(_, Builder.currentModule.get)) == Builder.currentModule) =>
      case Some(pb: SecretPortBinding) => // Ignore secret to not require visibility
      case Some(_: UnconstrainedBinding) =>
      case _ =>
        throwException(s"operand '$this' is not visible from the current module ${Builder.currentModule.get.name}")
    }
    if (!MonoConnect.checkWhenVisibility(this)) {
      throwException(s"operand has escaped the scope of the when in which it was constructed")
    }
  }

  private[chisel3] def lref: Node = {
    requireIsHardware(this)
    requireVisible()
    topBindingOpt match {
      case Some(binding: ReadOnlyBinding) =>
        throwException(s"internal error: attempted to generate LHS ref to ReadOnlyBinding $binding")
      case Some(ViewBinding(target)) => reify(target).lref
      case Some(binding: TopBinding) => Node(this)
      case opt => throwException(s"internal error: unknown binding $opt in generating LHS ref")
    }
  }

  private[chisel3] def ref: Arg = {
    def materializeWire(makeConst: Boolean = false): Arg = {
      if (!Builder.currentModule.isDefined) throwException(s"internal error: cannot materialize ref for $this")
      implicit val sourceInfo = UnlocatableSourceInfo
      if (makeConst) {
        WireDefault(Const(chiselTypeOf(this)), this).ref
      } else {
        WireDefault(this).ref
      }
    }
    requireIsHardware(this)
    topBindingOpt match {
      case Some(ViewBinding(target)) => reify(target).ref
      case Some(AggregateViewBinding(viewMap)) =>
        viewMap.get(this) match {
          case None => materializeWire() // FIXME FIRRTL doesn't have Aggregate Init expressions
          // This should not be possible because Element does the lookup in .topBindingOpt
          case x: Some[_] => throwException(s"Internal Error: In .ref for $this got '$topBindingOpt' and '$x'")
        }
      case Some(ElementLitBinding(litArg)) => litArg
      case Some(BundleLitBinding(litMap)) =>
        litMap.get(this) match {
          case Some(litArg) => litArg
          case _            => materializeWire(true) // FIXME FIRRTL doesn't have Bundle literal expressions
        }
      case Some(VecLitBinding(litMap)) =>
        litMap.get(this) match {
          case Some(litArg) => litArg
          case _            => materializeWire(true) // FIXME FIRRTL doesn't have Vec literal expressions
        }
      case Some(DontCareBinding()) =>
        materializeWire() // FIXME FIRRTL doesn't have a DontCare expression so materialize a Wire
      case Some(binding: TopBinding) =>
        if (Builder.currentModule.isDefined) {
          requireVisible()
        }
        Node(this)
      case opt => throwException(s"internal error: unknown binding $opt in generating LHS ref")
    }
  }

  private[chisel3] def setAllParents(parent: Option[BaseModule]): Unit =
    DataMirror.collectAllMembers(this).foreach { x => x._parent = parent }

  private[chisel3] def width: Width
  private[chisel3] def firrtlConnect(that: Data)(implicit sourceInfo: SourceInfo): Unit

  def cloneType: this.type

  private[chisel3] def cloneTypeFull: this.type = {
    val clone = this.cloneType // get a fresh object, without bindings
    clone.specifiedDirection = specifiedDirection
    probe.setProbeModifier(clone, probeInfo)
    clone.isConst = isConst
    clone
  }

  final def :=(that: => Data)(implicit sourceInfo: SourceInfo): Unit = {
    prefix(this) {
      this.connect(that)(sourceInfo)
    }
  }

  final def <>(that: => Data)(implicit sourceInfo: SourceInfo): Unit = {
    prefix(this) {
      this.bulkConnect(that)(sourceInfo)
    }
  }

  def isLit: Boolean = litOption.isDefined

  def litOption: Option[BigInt]

  def litValue: BigInt = litOption.get

  final def getWidth: Int =
    if (isWidthKnown) width.get else throwException(s"Width of $this is unknown!")

  final def isWidthKnown: Boolean = width.known

  final def widthOption: Option[Int] = if (isWidthKnown) Some(getWidth) else None

  def asTypeOf[T <: Data](that: T): T = macro SourceInfoTransform.thatArg

  def do_asTypeOf[T <: Data](that: T)(implicit sourceInfo: SourceInfo): T = {
    val thatCloned = Wire(that.cloneTypeFull)
    thatCloned.connectFromBits(this.asUInt)
    thatCloned
  }

  private[chisel3] def connectFromBits(
    that: Bits
  )(
    implicit sourceInfo: SourceInfo
  ): Unit

  final def asUInt: UInt = macro SourceInfoTransform.noArg

  private[chisel3] def _asUIntImpl(first: Boolean)(implicit sourceInfo: SourceInfo): UInt

  def do_asUInt(implicit sourceInfo: SourceInfo): UInt = this._asUIntImpl(true)

  def toPrintable: Printable

  def typeName: String = simpleClassName(this.getClass)
}

object Data {
  import scala.language.implicitConversions

  private[chisel3] case class ProbeInfo(val writable: Boolean)

  implicit class ConnectableDefault[T <: Data](consumer: T) extends connectable.ConnectableOperators[T](consumer)

  implicit class ConnectableVecDefault[T <: Data](consumer: Vec[T])
      extends connectable.ConnectableVecOperators[T](consumer)

  implicit def toConnectableDefault[T <: Data](d: T): Connectable[T] = Connectable.apply(d)

  implicit val dataMatchingZipOfChildren: DataMirror.HasMatchingZipOfChildren[Data] =
    new DataMirror.HasMatchingZipOfChildren[Data] {

      implicit class VecOptOps(vOpt: Option[Vec[Data]]) {
        def grab(i: Int): Option[Data] = vOpt.flatMap { _.lift(i) }
        def size = vOpt.map(_.size).getOrElse(0)
      }
      implicit class RecordOptGet(rOpt: Option[Record]) {
        def grab(k: String): Option[Data] = rOpt.flatMap { _._elements.get(k) }
        def keys: Iterable[String] = rOpt.map { r => r._elements.map(_._1) }.getOrElse(Seq.empty[String])
      }
      private def isDifferent(l: Option[Data], r: Option[Data]): Boolean =
        l.nonEmpty && r.nonEmpty && !isRecord(l, r) && !isVec(l, r) && !isElement(l, r) && !isProbe(l, r)
      private def isRecord(l: Option[Data], r: Option[Data]): Boolean =
        l.orElse(r).map { _.isInstanceOf[Record] }.getOrElse(false)
      private def isVec(l: Option[Data], r: Option[Data]): Boolean =
        l.orElse(r).map { _.isInstanceOf[Vec[_]] }.getOrElse(false)
      private def isElement(l: Option[Data], r: Option[Data]): Boolean =
        l.orElse(r).map { _.isInstanceOf[Element] }.getOrElse(false)
      private def isProbe(l: Option[Data], r: Option[Data]): Boolean =
        l.orElse(r).map { x => x.isInstanceOf[Data] && DataMirror.hasProbeTypeModifier(x) }.getOrElse(false)

      def matchingZipOfChildren(left: Option[Data], right: Option[Data]): Seq[(Option[Data], Option[Data])] =
        (left, right) match {
          case (None, None)                            => Nil
          case (lOpt, rOpt) if isDifferent(lOpt, rOpt) => Nil
          case (lOpt, rOpt) if isProbe(lOpt, rOpt)     => Nil
          case (lOpt: Option[Vec[Data] @unchecked], rOpt: Option[Vec[Data] @unchecked]) if isVec(lOpt, rOpt) =>
            (0 until (lOpt.size.max(rOpt.size))).map { i => (lOpt.grab(i), rOpt.grab(i)) }
          case (lOpt: Option[Record @unchecked], rOpt: Option[Record @unchecked]) if isRecord(lOpt, rOpt) =>
            (lOpt.keys ++ rOpt.keys).toList.distinct.map { k => (lOpt.grab(k), rOpt.grab(k)) }
          case (lOpt: Option[Element @unchecked], rOpt: Option[Element @unchecked]) if isElement(lOpt, rOpt) => Nil
          case _ =>
            throw new InternalErrorException(s"Match Error: left=$left, right=$right")
        }
    }

  implicit class DataEquality[T <: Data](lhs: T)(implicit sourceInfo: SourceInfo) {

    def ===(rhs: T): Bool = {
      (lhs, rhs) match {
        case (thiz: UInt, that: UInt) => thiz === that
        case (thiz: SInt, that: SInt) => thiz === that
        case (thiz: AsyncReset, that: AsyncReset) => thiz.asBool === that.asBool
        case (thiz: Reset, that: Reset) => thiz === that
        case (thiz: EnumType, that: EnumType) => thiz === that
        case (thiz: Clock, that: Clock) => thiz.asUInt === that.asUInt
        case (thiz: Vec[_], that: Vec[_]) =>
          if (thiz.length != that.length) {
            throwException(s"Cannot compare Vecs $thiz and $that: Vec sizes differ")
          } else {
            thiz.elementsIterator
              .zip(that.elementsIterator)
              .map { case (thisData, thatData) => thisData === thatData }
              .reduce(_ && _)
          }
        case (thiz: Record, that: Record) =>
          if (thiz._elements.size != that._elements.size) {
            throwException(s"Cannot compare Bundles $thiz and $that: Bundle types differ")
          } else {
            thiz._elements.map {
              case (thisName, thisData) =>
                if (!that._elements.contains(thisName))
                  throwException(
                    s"Cannot compare Bundles $thiz and $that: field $thisName (from $thiz) was not found in $that"
                  )

                val thatData = that._elements(thisName)

                try {
                  thisData === thatData
                } catch {
                  case e: chisel3.ChiselException =>
                    throwException(
                      s"Cannot compare field $thisName in Bundles $thiz and $that: ${e.getMessage.split(": ").last}"
                    )
                }
            }
              .reduce(_ && _)
          }
        case (_: DontCare.type, _: DontCare.type) => true.B

        case (thiz: Analog, that: Analog) =>
          throwException(s"Cannot compare Analog values $thiz and $that: Equality isn't defined for Analog values")
        case (thiz, that) => throwException(s"Cannot compare $thiz and $that: Runtime types differ")
      }
    }
  }
}

trait WireFactory {

  def apply[T <: Data](source: => T)(implicit sourceInfo: SourceInfo): T = {
    val prevId = Builder.idGen.value
    val t = source // evaluate once (passed by name)
    requireIsChiselType(t, "wire type")

    val x = if (!t.mustClone(prevId)) t else t.cloneTypeFull

    x.bind(WireBinding(Builder.forcedUserModule, Builder.currentWhen))

    pushCommand(DefWire(sourceInfo, x))

    x
  }
}


object Wire extends WireFactory

private[chisel3] sealed trait WireDefaultImpl {

  private def applyImpl[T <: Data](
    t:    T,
    init: Data
  )(
    implicit sourceInfo: SourceInfo
  ): T = {
    val x = Wire(t)
    requireIsHardware(init, "wire initializer")
    x := init
    x
  }

  def apply[T <: Data](
    t:    T,
    init: DontCare.type
  )(
    implicit sourceInfo: SourceInfo
  ): T = {
    applyImpl(t, init)
  }

  def apply[T <: Data](t: T, init: T)(implicit sourceInfo: SourceInfo): T = {
    applyImpl(t, init)
  }

  def apply[T <: Data](init: T)(implicit sourceInfo: SourceInfo): T = {
    val model = (init match {
      // If init is a literal without forced width OR any non-literal, let width be inferred
      case init: Bits if !init.litIsForcedWidth.getOrElse(false) => init.cloneTypeWidth(Width())
      case _ => init.cloneTypeFull
    }).asInstanceOf[T]
    apply(model, init)
  }
}

object WireDefault extends WireDefaultImpl

object WireInit extends WireDefaultImpl

final case object DontCare extends Element with connectable.ConnectableDocs {

  private[chisel3] override val width: Width = UnknownWidth()

  bind(DontCareBinding(), SpecifiedDirection.Output)
  override def cloneType: this.type = DontCare

  override def toString: String = "DontCare()"

  override def litOption: Option[BigInt] = None

  def toPrintable: Printable = PString("DONTCARE")

  private[chisel3] def connectFromBits(
    that: Bits
  )(
    implicit sourceInfo: SourceInfo
  ): Unit = {
    Builder.error("connectFromBits: DontCare cannot be a connection sink (LHS)")
  }

  override private[chisel3] def _asUIntImpl(first: Boolean)(implicit sourceInfo: SourceInfo): UInt = {
    Builder.error("DontCare does not have a UInt representation")
    0.U
  }

  final def :>=[T <: Data](producer: => T)(implicit sourceInfo: SourceInfo): Unit =
    this.asInstanceOf[Data] :>= producer.asInstanceOf[Data]
}
